///*
//Copyright (c) 2014, Ashley Mills
//All rights reserved.
//
//Redistribution and use in source and binary forms, with or without
//modification, are permitted provided that the following conditions are met:
//
//1. Redistributions of source code must retain the above copyright notice, this
//list of conditions and the following disclaimer.
//
//2. Redistributions in binary form must reproduce the above copyright notice,
//this list of conditions and the following disclaimer in the documentation
//and/or other materials provided with the distribution.
//
//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
//AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
//IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
//ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
//LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
//CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
//SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
//INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
//CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
//ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
//POSSIBILITY OF SUCH DAMAGE.
//*/
//
//import SystemConfiguration
//import Foundation
//
//enum ReachabilityError: Error {
//    case failedToCreateWithAddress(sockaddr, Int32)
//    case failedToCreateWithHostname(String, Int32)
//    case unableToSetCallback(Int32)
//    case unableToSetDispatchQueue(Int32)
//    case unableToGetFlags(Int32)
//}
//
//@available(*, unavailable, renamed: "Notification.Name.reachabilityChanged")
//let ReachabilityChangedNotification = NSNotification.Name("ReachabilityChangedNotification")
//
//extension Notification.Name {
//    static let reachabilityChanged = Notification.Name("reachabilityChanged")
//}
//
//class Reachability {
//
//    typealias NetworkReachable = (Reachability) -> ()
//    typealias NetworkUnreachable = (Reachability) -> ()
//
//    @available(*, unavailable, renamed: "Connection")
//    enum NetworkStatus: CustomStringConvertible {
//        case notReachable, reachableViaWiFi, reachableViaWWAN
//        var description: String {
//            switch self {
//            case .reachableViaWWAN: return "Cellular"
//            case .reachableViaWiFi: return "WiFi"
//            case .notReachable: return "No Connection"
//            }
//        }
//    }
//
//    enum Connection: CustomStringConvertible {
//        @available(*, deprecated, renamed: "unavailable")
//        case none
//        case unavailable, wifi, cellular
//        var description: String {
//            switch self {
//            case .cellular: return "Cellular"
//            case .wifi: return "WiFi"
//            case .unavailable: return "No Connection"
//            case .none: return "unavailable"
//            }
//        }
//    }
//
//    var whenReachable: NetworkReachable?
//    var whenUnreachable: NetworkUnreachable?
//
//    @available(*, deprecated, renamed: "allowsCellularConnection")
//    let reachableOnWWAN: Bool = true
//
//    /// Set to `false` to force Reachability.connection to .none when on cellular connection (default value `true`)
//    var allowsCellularConnection: Bool
//
//    // The notification center on which "reachability changed" events are being posted
//    var notificationCenter: NotificationCenter = NotificationCenter.default
//
//    @available(*, deprecated, renamed: "connection.description")
//    var currentReachabilityString: String {
//        return "\(connection)"
//    }
//
//    @available(*, unavailable, renamed: "connection")
//    var currentReachabilityStatus: Connection {
//        return connection
//    }
//
//    var connection: Connection {
//        if flags == nil {
//            try? setReachabilityFlags()
//        }
//        
//        switch flags?.connection {
//        case .unavailable?, nil: return .unavailable
//        case .none?: return .unavailable
//        case .cellular?: return allowsCellularConnection ? .cellular : .unavailable
//        case .wifi?: return .wifi
//        }
//    }
//
//    fileprivate var isRunningOnDevice: Bool = {
//        #if targetEnvironment(simulator)
//            return false
//        #else
//            return true
//        #endif
//    }()
//
//    fileprivate(set) var notifierRunning = false
//    fileprivate let reachabilityRef: SCNetworkReachability
//    fileprivate let reachabilitySerialQueue: DispatchQueue
//    fileprivate let notificationQueue: DispatchQueue?
//    fileprivate(set) var flags: SCNetworkReachabilityFlags? {
//        didSet {
//            guard flags != oldValue else { return }
//            notifyReachabilityChanged()
//        }
//    }
//
//    required init(reachabilityRef: SCNetworkReachability,
//                         queueQoS: DispatchQoS = .default,
//                         targetQueue: DispatchQueue? = nil,
//                         notificationQueue: DispatchQueue? = .main) {
//        self.allowsCellularConnection = true
//        self.reachabilityRef = reachabilityRef
//        self.reachabilitySerialQueue = DispatchQueue(label: "uk.co.ashleymills.reachability", qos: queueQoS, target: targetQueue)
//        self.notificationQueue = notificationQueue
//    }
//
//    convenience init(hostname: String,
//                            queueQoS: DispatchQoS = .default,
//                            targetQueue: DispatchQueue? = nil,
//                            notificationQueue: DispatchQueue? = .main) throws {
//        guard let ref = SCNetworkReachabilityCreateWithName(nil, hostname) else {
//            throw ReachabilityError.failedToCreateWithHostname(hostname, SCError())
//        }
//        self.init(reachabilityRef: ref, queueQoS: queueQoS, targetQueue: targetQueue, notificationQueue: notificationQueue)
//    }
//
//    convenience init(queueQoS: DispatchQoS = .default,
//                            targetQueue: DispatchQueue? = nil,
//                            notificationQueue: DispatchQueue? = .main) throws {
//        var zeroAddress = sockaddr()
//        zeroAddress.sa_len = UInt8(MemoryLayout<sockaddr>.size)
//        zeroAddress.sa_family = sa_family_t(AF_INET)
//
//        guard let ref = SCNetworkReachabilityCreateWithAddress(nil, &zeroAddress) else {
//            throw ReachabilityError.failedToCreateWithAddress(zeroAddress, SCError())
//        }
//
//        self.init(reachabilityRef: ref, queueQoS: queueQoS, targetQueue: targetQueue, notificationQueue: notificationQueue)
//    }
//
//    deinit {
//        stopNotifier()
//    }
//}
//
//extension Reachability {
//
//    // MARK: - *** Notifier methods ***
//    func startNotifier() throws {
//        guard !notifierRunning else { return }
//
//        let callback: SCNetworkReachabilityCallBack = { (reachability, flags, info) in
//            guard let info = info else { return }
//
//            // `weakifiedReachability` is guaranteed to exist by virtue of our
//            // retain/release callbacks which we provided to the `SCNetworkReachabilityContext`.
//            let weakifiedReachability = Unmanaged<ReachabilityWeakifier>.fromOpaque(info).takeUnretainedValue()
//
//            // The weak `reachability` _may_ no longer exist if the `Reachability`
//            // object has since been deallocated but a callback was already in flight.
//            weakifiedReachability.reachability?.flags = flags
//        }
//
//        let weakifiedReachability = ReachabilityWeakifier(reachability: self)
//        let opaqueWeakifiedReachability = Unmanaged<ReachabilityWeakifier>.passUnretained(weakifiedReachability).toOpaque()
//
//        var context = SCNetworkReachabilityContext(
//            version: 0,
//            info: UnsafeMutableRawPointer(opaqueWeakifiedReachability),
//            retain: { (info: UnsafeRawPointer) -> UnsafeRawPointer in
//                let unmanagedWeakifiedReachability = Unmanaged<ReachabilityWeakifier>.fromOpaque(info)
//                _ = unmanagedWeakifiedReachability.retain()
//                return UnsafeRawPointer(unmanagedWeakifiedReachability.toOpaque())
//            },
//            release: { (info: UnsafeRawPointer) -> Void in
//                let unmanagedWeakifiedReachability = Unmanaged<ReachabilityWeakifier>.fromOpaque(info)
//                unmanagedWeakifiedReachability.release()
//            },
//            copyDescription: { (info: UnsafeRawPointer) -> Unmanaged<CFString> in
//                let unmanagedWeakifiedReachability = Unmanaged<ReachabilityWeakifier>.fromOpaque(info)
//                let weakifiedReachability = unmanagedWeakifiedReachability.takeUnretainedValue()
//                let description = weakifiedReachability.reachability?.description ?? "nil"
//                return Unmanaged.passRetained(description as CFString)
//            }
//        )
//
//        if !SCNetworkReachabilitySetCallback(reachabilityRef, callback, &context) {
//            stopNotifier()
//            throw ReachabilityError.unableToSetCallback(SCError())
//        }
//
//        if !SCNetworkReachabilitySetDispatchQueue(reachabilityRef, reachabilitySerialQueue) {
//            stopNotifier()
//            throw ReachabilityError.unableToSetDispatchQueue(SCError())
//        }
//
//        // Perform an initial check
//        try setReachabilityFlags()
//
//        notifierRunning = true
//    }
//
//    func stopNotifier() {
//        defer { notifierRunning = false }
//
//        SCNetworkReachabilitySetCallback(reachabilityRef, nil, nil)
//        SCNetworkReachabilitySetDispatchQueue(reachabilityRef, nil)
//    }
//
//    // MARK: - *** Connection test methods ***
//    @available(*, deprecated, message: "Please use `connection != .none`")
//    var isReachable: Bool {
//        return connection != .unavailable
//    }
//
//    @available(*, deprecated, message: "Please use `connection == .cellular`")
//    var isReachableViaWWAN: Bool {
//        // Check we're not on the simulator, we're REACHABLE and check we're on WWAN
//        return connection == .cellular
//    }
//
//   @available(*, deprecated, message: "Please use `connection == .wifi`")
//    var isReachableViaWiFi: Bool {
//        return connection == .wifi
//    }
//
//    var description: String {
//        return flags?.description ?? "unavailable flags"
//    }
//}
//
//fileprivate extension Reachability {
//
//    func setReachabilityFlags() throws {
//        try reachabilitySerialQueue.sync { [unowned self] in
//            var flags = SCNetworkReachabilityFlags()
//            if !SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags) {
//                self.stopNotifier()
//                throw ReachabilityError.unableToGetFlags(SCError())
//            }
//            
//            self.flags = flags
//        }
//    }
//    
//
//    func notifyReachabilityChanged() {
//        let notify = { [weak self] in
//            guard let self = self else { return }
//            self.connection != .unavailable ? self.whenReachable?(self) : self.whenUnreachable?(self)
//            self.notificationCenter.post(name: .reachabilityChanged, object: self)
//        }
//
//        // notify on the configured `notificationQueue`, or the caller's (i.e. `reachabilitySerialQueue`)
//        notificationQueue?.async(execute: notify) ?? notify()
//    }
//}
//
//extension SCNetworkReachabilityFlags {
//
//    typealias Connection = Reachability.Connection
//
//    var connection: Connection {
//        guard isReachableFlagSet else { return .unavailable }
//
//        // If we're reachable, but not on an iOS device (i.e. simulator), we must be on WiFi
//        #if targetEnvironment(simulator)
//        return .wifi
//        #else
//        var connection = Connection.unavailable
//
//        if !isConnectionRequiredFlagSet {
//            connection = .wifi
//        }
//
//        if isConnectionOnTrafficOrDemandFlagSet {
//            if !isInterventionRequiredFlagSet {
//                connection = .wifi
//            }
//        }
//
//        if isOnWWANFlagSet {
//            connection = .cellular
//        }
//
//        return connection
//        #endif
//    }
//
//    var isOnWWANFlagSet: Bool {
//        #if os(iOS)
//        return contains(.isWWAN)
//        #else
//        return false
//        #endif
//    }
//    var isReachableFlagSet: Bool {
//        return contains(.reachable)
//    }
//    var isConnectionRequiredFlagSet: Bool {
//        return contains(.connectionRequired)
//    }
//    var isInterventionRequiredFlagSet: Bool {
//        return contains(.interventionRequired)
//    }
//    var isConnectionOnTrafficFlagSet: Bool {
//        return contains(.connectionOnTraffic)
//    }
//    var isConnectionOnDemandFlagSet: Bool {
//        return contains(.connectionOnDemand)
//    }
//    var isConnectionOnTrafficOrDemandFlagSet: Bool {
//        return !intersection([.connectionOnTraffic, .connectionOnDemand]).isEmpty
//    }
//    var isTransientConnectionFlagSet: Bool {
//        return contains(.transientConnection)
//    }
//    var isLocalAddressFlagSet: Bool {
//        return contains(.isLocalAddress)
//    }
//    var isDirectFlagSet: Bool {
//        return contains(.isDirect)
//    }
//    var isConnectionRequiredAndTransientFlagSet: Bool {
//        return intersection([.connectionRequired, .transientConnection]) == [.connectionRequired, .transientConnection]
//    }
//
//    var description: String {
//        let W = isOnWWANFlagSet ? "W" : "-"
//        let R = isReachableFlagSet ? "R" : "-"
//        let c = isConnectionRequiredFlagSet ? "c" : "-"
//        let t = isTransientConnectionFlagSet ? "t" : "-"
//        let i = isInterventionRequiredFlagSet ? "i" : "-"
//        let C = isConnectionOnTrafficFlagSet ? "C" : "-"
//        let D = isConnectionOnDemandFlagSet ? "D" : "-"
//        let l = isLocalAddressFlagSet ? "l" : "-"
//        let d = isDirectFlagSet ? "d" : "-"
//
//        return "\(W)\(R) \(c)\(t)\(i)\(C)\(D)\(l)\(d)"
//    }
//}
//
///**
// `ReachabilityWeakifier` weakly wraps the `Reachability` class
// in order to break retain cycles when interacting with CoreFoundation.
//
// CoreFoundation callbacks expect a pair of retain/release whenever an
// opaque `info` parameter is provided. These callbacks exist to guard
// against memory management race conditions when invoking the callbacks.
//
// #### Race Condition
//
// If we passed `SCNetworkReachabilitySetCallback` a direct reference to our
// `Reachability` class without also providing corresponding retain/release
// callbacks, then a race condition can lead to crashes when:
// - `Reachability` is deallocated on thread X
// - A `SCNetworkReachability` callback(s) is already in flight on thread Y
//
// #### Retain Cycle
//
// If we pass `Reachability` to CoreFoundtion while also providing retain/
// release callbacks, we would create a retain cycle once CoreFoundation
// retains our `Reachability` class. This fixes the crashes and his how
// CoreFoundation expects the API to be used, but doesn't play nicely with
// Swift/ARC. This cycle would only be broken after manually calling
// `stopNotifier()` — `deinit` would never be called.
//
// #### ReachabilityWeakifier
//
// By providing both retain/release callbacks and wrapping `Reachability` in
// a weak wrapper, we:
// - interact correctly with CoreFoundation, thereby avoiding a crash.
// See "Memory Management Programming Guide for Core Foundation".
// - don't alter the public API of `Reachability.swift` in any way
// - still allow for automatic stopping of the notifier on `deinit`.
// */
//private class ReachabilityWeakifier {
//    weak var reachability: Reachability?
//    init(reachability: Reachability) {
//        self.reachability = reachability
//    }
//}
